package cn.sc.summer.gateway.webfilter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.sc.summer.constant.model.Result;
import cn.sc.summer.constant.token.AuthProperties;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.MatchUtil;
import cn.sc.summer.redis.util.RedisHelper;
import cn.sc.summer.token.constant.APIConstant;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.po.UserDetailX;
import cn.sc.summer.token.properties.TokenMaxProperties;
import cn.sc.summer.token.util.UserUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 类名：token校验
 *
 * @author a-xin
 * @date 2023/11/24 13:20
 */
@Slf4j
public class AuthFilter implements WebFilter {

    /**
     * 默认校验失败信息
     */
    private static final String ERROR_MESSAGE = "You haven't logged in for a long time, please log in again!";

    /**
     * 默认token续期时间
     */
    private static final Integer DEFAULT_EXPIRE_TIME = 4;

    private final AuthProperties authProperties;

    private final TokenMaxProperties tokenMaxProperties;

    public AuthFilter(AuthProperties authProperties, TokenMaxProperties tokenMaxProperties) {
        this.authProperties = authProperties;
        this.tokenMaxProperties = tokenMaxProperties;
    }

    @Override
    @NonNull
    public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getPath().value();
        log.info("-> auth provider: {}", path);
        if (MatchUtil.anyMatchValue(path, APIConstant.SWAGGER_SOURCE)
                || MatchUtil.anyMatchValue(path, authProperties.getAuthIgnore())) {
            return chain.filter(exchange);
        }

        String token = request.getHeaders().getFirst(TokenConstant.ACCESS_TOKEN);

        UserDetailX loginUser = UserUtil.getLoginUser(token);
        if (ObjectUtil.isNull(loginUser)) {
            return writeFailed(response, "Not login!");
        }
        String redisKey = loginUser.getLoginType() + ":" + loginUser.getUserId();
        if (!RedisHelper.hasKey(redisKey)) {
            return writeFailed(response, ERROR_MESSAGE);
        }

        Double score = RedisHelper.zGetScore(redisKey, token);
        if (score == null || score < System.currentTimeMillis()) {
            RedisHelper.zRemoveMember(redisKey, token);
            RedisHelper.del(TokenConstant.ACCESS_TOKEN_PREFIX + token);
            return writeFailed(response, ERROR_MESSAGE);
        }
        if (!loginUser.getUserId().equals(TokenUtil.analysisSimpleToken(token))) {
            RedisHelper.zRemoveMember(redisKey, token);
            RedisHelper.del(TokenConstant.ACCESS_TOKEN_PREFIX + token);
            return writeFailed(response, "The token is invalid！");
        }

        Integer EXPIRE_TIME;
        Map<String, Map<String, Integer>> config = tokenMaxProperties.getConfig();
        if (CollUtil.isNotEmpty(config)) {
            Map<String, Integer> tokenInfoMap = tokenMaxProperties.getConfig().getOrDefault(loginUser.getLoginType().toLowerCase(), new HashMap<>());
            if (CollUtil.isNotEmpty(tokenInfoMap)) {
                EXPIRE_TIME = tokenInfoMap.getOrDefault("expireTime", DEFAULT_EXPIRE_TIME);
            } else {
                EXPIRE_TIME = DEFAULT_EXPIRE_TIME;
            }
        } else {
            EXPIRE_TIME = DEFAULT_EXPIRE_TIME;
        }

        Long expire = RedisHelper.getExpire(TokenConstant.ACCESS_TOKEN_PREFIX + token);
        if (expire < 60 * 5) {
            //token续期
            RedisHelper.expire(TokenConstant.ACCESS_TOKEN_PREFIX + token, EXPIRE_TIME * 60 * 60 * 1000 + 1000L, TimeUnit.MILLISECONDS);
        }

        return chain.filter(exchange);
    }

    /**
     * 获取异常
     *
     * @param response 返回载体
     * @param reason   失败信息
     */
    private Mono<Void> writeFailed(ServerHttpResponse response, String reason) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.writeWith(Mono.just(response.bufferFactory().wrap(JSON.toJSONString(Result.fail(reason)).getBytes())));
    }

}
